package main

import (
	"database/sql"
	_ "github.com/go-sql-driver/mysql"
	"github.com/gin-gonic/gin"
	"github.com/iGoogle-ink/gopay"
	"github.com/iGoogle-ink/gopay/alipay"
	"github.com/iGoogle-ink/gopay/wechat"
	qrcode "github.com/skip2/go-qrcode"
	"log"
	"math/rand"
	"net/http"
	"strings"
	"time"
	"./config"
)

var (
	wxc *wechat.Client
	alic *alipay.Client
	wxconf *config.PayConf
	aliconf *config.PayConf
	server string
	isProd bool
)

type Payment struct {
	productid int `json:"productid" form:"productid" binding:"required"`
	paytype   int `json:"paytype" form:"paytype" binding:"required"`
	fee       int `json:"fee" form:"fee" binding:"required"`
}

func init() {
	//wxconf, _ = config.GetPayConf("wxpay")
	aliconf, _ = config.GetPayConf("alipay")
	isProd = config.GetEnv()
	server = config.GetServerURI()

	//wxc = wechat.NewClient(wxconf.AppId, wxconf.MchId, wxconf.ApiKey, isProd)
	//wxc.AddCertFilePath(certFilePath, keyFilePath, pkcs12FilePath)

	alic = alipay.NewClient(aliconf.AppId, aliconf.PrivateKey, isProd)
}

func Order(c *gin.Context) {
	var p Payment
	// Bind payment
	if err := c.ShouldBind(&p); err != nil {
		c.String(http.StatusBadRequest, "%v", err)
		log.Fatalln(err)
	}
	// Generate order num
	number := genOrderNum()
	if p.paytype == 0 {
		// 初始化 BodyMap
		bm := make(gopay.BodyMap)
		bm.Set("nonce_str", gopay.GetRandomString(32))
		bm.Set("body", "测试支付")
		bm.Set("out_trade_no", number)
		bm.Set("total_fee", p.fee)
		bm.Set("spbill_create_ip", c.ClientIP())
		bm.Set("notify_url", server+"/pay/wxnotify")
		bm.Set("trade_type", "NATIVE")
		bm.Set("sign_type", "MD5")

		// 参数 sign ，可单独生成赋值到BodyMap中；也可不传sign参数，client内部会自动获取
		// 如需单独赋值 sign 参数，需通过下面方法，最后获取sign值并在最后赋值此参数
		sign := wechat.GetParamSign(wxconf.AppId, wxconf.MchId, wxconf.ApiKey, bm)
		// sign, _ := wechat.GetSanBoxParamSign("wxdaa2ab9ef87b5497", mchId, apiKey, body)
		bm.Set("sign", sign)

		// 统一下单
		wxRsp, err := wxc.UnifiedOrder(bm)
		if err != nil {
			c.String(http.StatusBadGateway, "err:%v", err)
			log.Fatalln(err)
		}
		if wxRsp.ReturnCode == "SUCCESS" {
			if wxRsp.ResultCode == "SUCCESS" {
				// store order info
				stmt, err := db.Prepare(`INSERT INTO goadmin_order(appid, mch_id, out_trade_no, trade_type, prepay_id, total_fee, product_id) VALUES (?,?,?,?,?,?,?)`)
				if err != nil {
					c.String(http.StatusBadGateway, "DB insert err:%v", err)
					log.Fatalln(wxRsp.ReturnMsg, err)
				}
				_, err = stmt.Exec(wxRsp.Appid, wxRsp.MchId, number, wxRsp.TradeType, wxRsp.PrepayId, p.fee, p.productid)
				if err != nil {
					c.String(http.StatusBadGateway, "DB insert err:%v", err)
					log.Fatalln(wxRsp.ReturnMsg, err)
				}
				stmt.Close()
				// generate qrcode
				var png []byte
				png, err = qrcode.Encode(wxRsp.CodeUrl, qrcode.Medium, 256)
				if err != nil {
					c.String(http.StatusBadGateway, "qrcode generate failed :%v", err)
					log.Fatalf("QRCode generate fail, url:%s err:%v\n", wxRsp.CodeUrl, err)
				}
				c.JSON(http.StatusOK, gin.H{
					"status": 10200,
					"msg":    "order success",
					"qrcode": png,
				})
				return
			}
			c.String(http.StatusBadGateway, "wx response err code:%v, des:%v", wxRsp.ErrCode, wxRsp.ErrCodeDes)
			log.Fatalf("wx response err code:%v, des:%v\n", wxRsp.ErrCode, wxRsp.ErrCodeDes)
		}
		c.String(http.StatusBadGateway, "wx response err msg:%v", wxRsp.ReturnMsg)
		log.Fatalf("wx response err msg:%v\n", wxRsp.ReturnMsg)
	}
	if p.paytype == 1 {
		// 初始化 BodyMap
		bm := make(gopay.BodyMap)
		bm.Set("subject", "测试支付")
		bm.Set("out_trade_no", number)
		bm.Set("total_amount", p.fee)
		bm.Set("notify_url", server+"/pay/alipaynotify")

		aliRsp, err := alic.TradePrecreate(bm)
		if err != nil {
			c.String(http.StatusBadGateway, "err:%v", err)
			log.Fatalln(err)
		}
		if aliRsp.Response.Code == "10000" {
			// store order info
			stmt, err := db.Prepare(`INSERT INTO goadmin_order(appid, mch_id, out_trade_no, trade_type, prepay_id, total_fee, product_id, order_type) VALUES (?,?,?,?,?,?,?,?)`)
			if err != nil {
				c.String(http.StatusBadGateway, "DB insert err:%v", err)
				log.Fatalln("ali pretrade no: ", aliRsp.Response.OutTradeNo, err)
			}
			_, err = stmt.Exec(aliconf.AppId, "", aliRsp.Response.OutTradeNo, "", "", p.fee, p.productid, p.paytype)
			if err != nil {
				c.String(http.StatusBadGateway, "DB insert err:%v", err)
				log.Fatalln("ali pretrade no: ", aliRsp.Response.OutTradeNo, err)
			}
			stmt.Close()
			// generate qrcode
			var png []byte
			png, err = qrcode.Encode(aliRsp.Response.QrCode, qrcode.Medium, 256)
			if err != nil {
				c.String(http.StatusBadGateway, "qrcode generate failed :%v", err)
				log.Fatalf("QRCode generate fail, url:%s err:%v\n", aliRsp.Response.QrCode, err)
			}
			c.JSON(http.StatusOK, gin.H{
				"status": 10200,
				"msg":    "order success",
				"qrcode": png,
			})
		}
		c.String(http.StatusBadGateway, "ali response err code:%v msg:%v", aliRsp.Response.Code, aliRsp.Response.Msg)
		log.Fatalf("ali response code:%v msg:%v subcode:%v submsg:%v\n", aliRsp.Response.Code, aliRsp.Response.Msg, aliRsp.Response.SubCode, aliRsp.Response.SubMsg)
	}
	c.String(http.StatusBadGateway, "paytype error, only 0/1 applied")
}

func genOrderNum() string {
	str := "0123456789"
	var (
		result []byte
		b      []byte
		r      *rand.Rand
	)
	b = []byte(str)
	r = rand.New(rand.NewSource(time.Now().UnixNano()))
	for i := 0; i < 10; i++ {
		result = append(result, b[r.Intn(len(b))])
	}
	return time.Now().Format("20060102150405") + string(result)
}

func WxpayNotify(c *gin.Context) {
	// ====支付异步通知参数解析和验签Sign====
	// 解析支付异步通知的参数
	//    req：*http.Request
	//    返回参数 notifyReq：通知的参数
	//    返回参数 err：错误信息
	bm, err := wechat.ParseNotifyToBodyMap(c.Request)
	// 验签操作
	ok, err := wechat.VerifySign(wxconf.ApiKey, wechat.SignType_MD5, bm)
	if !ok {
		log.Fatalln("wx verify pay notify sign err:", err)
	}
	// check if fund paied
	var status int8
	err = db.QueryRow(`SELECT status FROM goadmin_order WHERE out_trade_no=? LIMIT 1`,
		bm.Get("out_trade_no")).Scan(&status)
	if err != nil && err != sql.ErrNoRows {
		log.Fatalf("order %s not found\n", bm.Get("out_trade_no"))
	}
	if status != 0 {
		log.Fatalln("order %s is not in payable state")
	}
	// return code
	c.String(http.StatusOK, toXmlString(bm.Get("return_code"), bm.Get("return_msg")))
	// store payment info
	stmt, err := db.Prepare(`INSERT INTO goadmin_payment(appid, mch_id, openid, buyer_account, out_trade_no, transaction_id,
		is_subscribe, trade_type, bank_type, total_fee, cash_fee, time_end) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)`)
	if err != nil {
		c.String(http.StatusBadGateway, "DB insert err:%v", err)
		log.Fatalln(bm.EncodeGetParams(), err)
	}
	_, err = stmt.Exec(bm.Get("appid"), bm.Get("mch_id"), bm.Get("openid"), "",
		bm.Get("out_trade_no"), bm.Get("transaction_id"), bm.Get("is_subscribe"),
		bm.Get("trade_type"), bm.Get("bank_type"), bm.Get("total_fee"), bm.Get("cash_fee"), bm.Get("end_time"))
	if err != nil {
		c.String(http.StatusBadGateway, "DB insert err:%v", err)
		log.Fatalln(bm.EncodeGetParams(), err)
	}
	stmt.Close()
	// update order status
	tx, err := db.Begin()
	if err != nil {
		log.Fatalln("begin translation err:", err)
	}
	stmt, err = tx.Prepare(`UPDATE goadmin_order SET status=? WHERE out_trade_no=?`)
	if err != nil {
		tx.Rollback()
		log.Fatalln("update order fail ", bm.Get("trade_out_no"), err)
	}
	_, err = stmt.Exec(1, bm.Get("trade_out_no"))
	if err != nil {
		tx.Rollback()
		log.Fatalln("update order fail ", bm.Get("trade_out_no"), err)
	} else {
		tx.Commit()
	}
}

func AlipayNotify(c *gin.Context) {
	// receive alipay notify
	// ====异步通知参数解析和验签Sign====
	// 解析异步通知的参数
	//    req：*http.Request
	//    返回参数 notifyReq：通知的参数
	//    返回参数 err：错误信息
	bm, err := alipay.ParseNotifyToBodyMap(c.Request)
	// 验签操作
	ok, err := alipay.VerifySign(aliconf.PublicKey, bm)
	// ==异步通知，返回支付宝平台的信息==
	c.String(http.StatusOK, "success")
	if !ok {
		log.Fatalln("alipay verify pay notify sign err:", err)
	}
	// check if fund paied
	var status int8
	err = db.QueryRow(`SELECT status FROM goadmin_order WHERE out_trade_no=? LIMIT 1`,
		bm.Get("out_trade_no")).Scan(&status)
	if err != nil && err != sql.ErrNoRows {
		log.Fatalf("order %s not found\n", bm.Get("out_trade_no"))
	}
	if status != 0 {
		log.Fatalln("order %s is not in payable state")
	}
	// store payment info
	stmt, err := db.Prepare(`INSERT INTO goadmin_payment(appid, mch_id, openid, buyer_account, out_trade_no, transaction_id,
		is_subscribe, trade_type, bank_type, total_fee, cash_fee, time_end) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)`)
	if err != nil {
		c.String(http.StatusBadGateway, "DB insert err:%v", err)
		log.Fatalln(bm.EncodeGetParams(), err)
	}
	_, err = stmt.Exec(bm.Get("app_id"), bm.Get("seller_id"), bm.Get("buyer_id"),
		bm.Get("buyer_logon_id"), bm.Get("out_trade_no"), bm.Get("trade_no"), "0", "", "",
		bm.Get("total_amount"), bm.Get("buyer_pay_amount"), bm.Get("gmt_payment"))
	if err != nil {
		c.String(http.StatusBadGateway, "DB insert err:%v", err)
		log.Fatalln(bm.EncodeGetParams(), err)
	}
	stmt.Close()
	// update order status
	tx, err := db.Begin()
	if err != nil {
		log.Fatalln("begin translation err:", err)
	}
	stmt, err = tx.Prepare(`UPDATE goadmin_order SET status=? WHERE out_trade_no=?`)
	if err != nil {
		tx.Rollback()
		log.Fatalln("update order fail ", bm.Get("trade_out_no"), err)
	}
	_, err = stmt.Exec(1, bm.Get("trade_out_no"))
	if err != nil {
		tx.Rollback()
		log.Fatalln("update order fail ", bm.Get("trade_out_no"), err)
	} else {
		tx.Commit()
	}
}

// 返回数据给微信
func toXmlString(returnCode, returnMsg string) (xmlStr string) {
	var buffer strings.Builder
	buffer.WriteString("<xml><return_code><![CDATA[")
	buffer.WriteString(returnCode)
	buffer.WriteString("]]></return_code>")
	buffer.WriteString("<return_msg><![CDATA[")
	buffer.WriteString(returnMsg)
	buffer.WriteString("]]></return_msg></xml>")
	xmlStr = buffer.String()
	return
}